﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using haptic.src.engine.maths;
using haptic.src.engine.core;
using haptic.src.engine.threads;
using System.Xml.Serialization;
using haptic.src.engine.scene;

namespace haptic.src.engine.animation
{
    public class Animation
    {
        #region Constructors
        public Animation(string sAnimFilePath)
        {
            m_sAnimFilePath = sAnimFilePath;
            m_oAnimRequest = new AnimationJob.AnimationRequest(this, 1.0f);
        }
        public Animation(AnimationData oAnimData)
        {
            m_oData = oAnimData;
            m_oAnimRequest = new AnimationJob.AnimationRequest(this, 1.0f);
        }
        public Result Load()
        {
            Debug.Assert(m_oData != null);
            m_oNodeAnimations.Clear();
            foreach (NodeAnimationData oNodeAnimData in m_oData.m_oNodeAnimationData)
            {
                INodeAnimation oNodeAnim;
                if (oNodeAnimData.GetType() == typeof(KeyframeNodeAnimationData))
                    oNodeAnim = new KeyframeNodeAnimation((KeyframeNodeAnimationData)(oNodeAnimData));
                else
                    oNodeAnim = new PhysicsNodeAnimation((PhysicsNodeAnimationData)(oNodeAnimData));

                if (oNodeAnim != null)
                    m_oNodeAnimations.Add(oNodeAnim);
            }
            return Result.Success;
        }
        #endregion

        #region Update
        public void Update(GameTime oTime)
        {
            m_fTime += (float)oTime.ElapsedGameTime.TotalSeconds;
        }
        #endregion

        #region Actions
        public void Apply(AnimationResult oRes, float fRatio)
        {
            if (fRatio <= 0.01f)
                return;

            // No blend
            if (fRatio >= 0.99f)
            {
                foreach(INodeAnimation oNodeAnim in m_oNodeAnimations)
                {
                    int iNodeIndex;
                    Vector2 vTranslation;
                    float fRotation;
                    oNodeAnim.GetFrameInfo(m_fTime, out iNodeIndex, out vTranslation, out fRotation);
                    AnimationResult.NodeResult oNodeRes = oRes.m_oResults[iNodeIndex];
                    oNodeRes.m_vTrans = vTranslation;
                    oNodeRes.m_fRot = fRotation;
                }
            }
            // Blend
            else
            {
                foreach (INodeAnimation oNodeAnim in m_oNodeAnimations)
                {
                    int iNodeIndex;
                    Vector2 vTranslation;
                    float fRotation;
                    oNodeAnim.GetFrameInfo(m_fTime, out iNodeIndex, out vTranslation, out fRotation);
                    AnimationResult.NodeResult oNodeRes = oRes.m_oResults[iNodeIndex];
                    oNodeRes.m_vTrans += (vTranslation - oNodeRes.m_vTrans) * fRatio;
                    oNodeRes.m_fRot += (fRotation - oNodeRes.m_fRot) * fRatio;
                }
            }
        }
        #endregion

        #region Attributes
        public string m_sAnimFilePath;
        private AnimationData m_oData;
        public AnimationData oData { get { return m_oData; } }
        private List<INodeAnimation> m_oNodeAnimations = new List<INodeAnimation>();
        public List<INodeAnimation> oNodeAnimations { get { return m_oNodeAnimations; } }
        private float m_fTime = 0.0f;
        #region Cache
        [XmlIgnore]
        public AnimationJob.AnimationRequest m_oAnimRequest;
        #endregion
        #endregion
    }

    public class AnimationResult
    {
        public class NodeResult
        {
            public Vector2 m_vTrans = Vector2.Zero;
            public float m_fRot = 0.0f;
            public Mat2x3 m_mFinal;
        }
        public void Reset()
        {
            foreach(NodeResult oNodeRes in m_oResults)
            {
                oNodeRes.m_vTrans = Vector2.Zero;
                oNodeRes.m_fRot = 0.0f;
            }
        }
        public void PrepareResults()
        {
            foreach (NodeResult oNodeRes in m_oResults)
            {
                oNodeRes.m_mFinal = new Mat2x3(oNodeRes.m_fRot, oNodeRes.m_vTrans);
            }
        }
        public void SendResults(List<Node> oNodes)
        {
            Debug.Assert(m_oResults.Count == oNodes.Count);
            for(int i=0; i<m_oResults.Count; i++)
            {
                NodeResult oNodeResult = m_oResults[i];
                Node oNode = oNodes[i];
                oNode.mLocal = oNodeResult.m_mFinal;
            }
        }
        #region Attributes
        public List<NodeResult> m_oResults;
        #endregion
    }

    public class AnimationJob : Job
    {
        #region Definitions
        public class AnimationRequest
        {
            public AnimationRequest(Animation oAnim, float fRatio)
            {
                m_oAnimation = oAnim;
                m_fRatio = fRatio;
            }
            public Animation m_oAnimation;
            public float m_fRatio = 1.0f;
        }
        #endregion

        #region Constructors
        public AnimationJob(Node oGraph)
        {
            m_oGraph = oGraph;
        }
        #endregion

        #region Edit
        public void AddAnimRequest(AnimationRequest oRequest)
        {
            m_oRequests.Add(oRequest);
        }
        #endregion

        #region Actions
        public Result Compute()
        {
            m_oAnimResult.Reset();
            foreach (AnimationRequest oRequest in m_oRequests)
            {
                oRequest.m_oAnimation.Apply(m_oAnimResult, oRequest.m_fRatio);
            }
            m_oAnimResult.PrepareResults();
            m_oRequests.Clear();
            return Result.Success;
        }
        public Result SendResults()
        {
            if (m_oGraph == null)
                return Result.Success;
            Debug.Assert(m_oGraph.eType == Node.NodeType.E_ROOT);
            m_oAnimResult.SendResults(((RootNode)m_oGraph).m_oNodes);
            return Result.Success;
        }
        #endregion

        #region Attributes
        private List<AnimationRequest> m_oRequests;
        private AnimationResult m_oAnimResult = new AnimationResult();
        private Node m_oGraph;
        #endregion
    }
}
